package com.sunreal.paygateway.api.controller;

import java.io.File;
import java.io.IOException;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.utils.qrcode.QrcodeUtils;
import com.sunreal.paygateway.common.AssertUtil;
import com.sunreal.paygateway.common.PayConstant;
import com.sunreal.paygateway.dao.model.MchInfo;
import com.sunreal.paygateway.dao.model.PayChannel;
import com.sunreal.paygateway.dao.model.PayOrder;
import com.sunreal.paygateway.dao.vo.SunrealPayOrder;
import com.sunreal.paygateway.service.BaseService;
import com.sunreal.paygateway.service.SunrealPayOrderService;
import com.sunreal.paygateway.service.channel.wechat.WxPayProperties;
import com.sunreal.paygateway.sys.annotations.RequestSign;
import com.sunreal.paygateway.sys.annotations.ResponseSign;
import com.sunreal.paygateway.sys.exception.BusinessException;
import com.sunreal.paygateway.sys.exception.WebErrCode;
import com.sunreal.paygateway.sys.util.AmountUtil;
import com.sunreal.paygateway.sys.util.GsonUtil;
import com.sunreal.paygateway.sys.util.result.WebResult;


/**
 * 功能描述：聚合支付统一下单控制器
 *
 * @Author 彭广
 * @Date 2019/4/16
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@Slf4j
public class SunrealPayOrderController {

    @Autowired
    private SunrealPayOrderService payOrderService;
    @Autowired
    private BaseService baseService;
    @Resource
    private WxPayProperties wxPayProperties;
    /**
     * 统一下单，在支付中心创建订单信息
     *
     * @param payorder 订单详情
     * @return 支付中心的订单ID
     */
    @PostMapping(value = "/api/pay/create_order", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    @RequestSign
    @ResponseSign
    public WebResult craetePayOrder(@RequestBody SunrealPayOrder payorder) {
        try {
            log.debug(GsonUtil.toJson(payorder));
            StringBuffer resKey = new StringBuffer();
            validateParams(payorder, resKey);
            String payOrderId = payOrderService.createPayOrder(payorder);
            if (StringUtils.isNotBlank(payOrderId)) {
                return WebResult.retSuccess(PayConstant.RET_MSG_SUCESS, resKey.toString(), payOrderId);
            } else {
                return WebResult.retFail(WebErrCode.ERROR_PARAMS);
            }
        } catch (Exception e) {
            return WebResult.retFail(e);
        }
    }

    /**
     * 订单支付页面，根据订单ID，向第三方支付进行下单，返回下单结果
     *
     * @param orderid 订单ID
     */
    @PostMapping(value = "/api/pay/{id}")
    @ResponseSign
    public WebResult payOrder(@PathVariable(value = "id") String orderid) {
        try {
            AssertUtil.notBlank(orderid, "支付订单ID不能为空！");
            StringBuffer resKey = new StringBuffer();
            Map<String, Object> payResult = payOrderService.payOrder(orderid, resKey);
            return WebResult.retSuccess(PayConstant.RET_MSG_SUCESS, resKey.toString(), payResult);
        } catch (Exception e) {
            log.error("打开支付页面失败！", e);
            return WebResult.retFail(e);
        }
    }

    /**
     * 针对PC端，根据订单ID，向第三方支付进行下单，返回待支付页面
     *
     * @param orderid
     * @param httpResponse
     */
    @GetMapping(value = "/api/pcpay/{id}")
    public void pcPayOrder(@PathVariable(value = "id") String orderid, HttpServletResponse httpResponse) {
        try {
            WebResult result = payOrder(orderid);
            PayOrder order = baseService.baseSelectPayOrder(orderid);
            Map<String, String> data = (Map<String, String>) result.getData();
            String tradeType = data.get("tradeType");
            String aliFormData = data.get("PAYFORM");
            String formData = "";
            if (tradeType != null) {//微信
                if (PayConstant.PAY_CHANNEL_WX_NATIVE.equalsIgnoreCase(tradeType.toString())) {
                    String codeURL = data.get("codeURL");
                    formData = createWXFormData(order, codeURL);
                }
            } else if (aliFormData != null) {
                formData = aliFormData;
            }
            httpResponse.setContentType("text/html;charset=UTF-8");
            httpResponse.getWriter().write(formData);//直接将完整的表单html输出到页面、
            httpResponse.getWriter().flush();
            httpResponse.getWriter().close();
        } catch (Exception e) {
            log.error("打开支付页面失败！", e);
        }
    }

    /**
     * 微信PC支付时，生成支付二维码方法，请求路径与{@link com.sunreal.paygateway.api.controller.SunrealPayOrderController#createWXFormData(PayOrder, String) 生成微信支付页面}方法中的js请求路径需保持一致
     * @param qrvalue 微信订单生产返回的二维码连接 code_url 一般类似“weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00”
     * @param httpResponse 直接将生成的二维码回写到页面
     * @throws IOException 回写失败抛异常
     */
    @RequestMapping(value = "/api/createQrcode")
    public void createQrcode(String qrvalue, HttpServletResponse httpResponse) throws IOException {
        File logofile = new File(wxPayProperties.getLogoFilePath());
        byte[] buf = QrcodeUtils.createQrcode(qrvalue, logofile);
        ServletOutputStream out = httpResponse.getOutputStream();
        out.write(buf);
        out.flush();
        out.close();
    }

    /**
     * 微信PC支付时，校验用户是否已经扫码支付完成的方法，请求路径与{@link com.sunreal.paygateway.api.controller.SunrealPayOrderController#createWXFormData(PayOrder, String) 生成微信支付页面}方法中的js请求路径需保持一致
     * @param mchId 商户ID
     * @param payOrderId 订单ID
     * @return Y-已经支付完成；N-没有支付完成
     */
    @RequestMapping(value = "/api/pay/isOrderFinish")
    public String queryPayOrder(@RequestParam String mchId, String payOrderId) {
        log.info("查询参数，mchId:{}" + mchId);
        log.info("查询参数，payOrderId:{}" + payOrderId);
        PayOrder payOrder = baseService.baseSelectPayOrderByMchIdAndMchOrderNo(mchId, payOrderId);
        log.info("查询支付订单,结果:{}", payOrder);
        if (payOrder != null && payOrder.getStatus() != null && payOrder.getStatus() > 1) {
            return "Y";
        } else {
            return "N";
        }
    }

    /**
     * 生成微信支付页面，页面中包含两个方法<p/>
     * 一个是根据微信支付返回的二维码连接{@link com.sunreal.paygateway.api.controller.SunrealPayOrderController#createQrcode(String, javax.servlet.http.HttpServletResponse) 生成的支付二维码}<br/>
     * 一个是{@link com.sunreal.paygateway.api.controller.SunrealPayOrderController#queryPayOrder(String, String) 校验用户是否已经扫描二维码并且完成支付}
     * @param order  订单信息
     * @param codeURL 微信订单生产返回的二维码连接
     * @return 一个HTML页面
     */
    private String createWXFormData(PayOrder order, String codeURL) {
        String orderid = order.getPayorderid();
        String mchId = order.getMchid();
        String subject = order.getSubject();
        String body = order.getBody();
        String returnUrl = order.getReturnurl();
        String amount = AmountUtil.convertCent2Dollar(order.getAmount().toString());
        StringBuilder url = new StringBuilder("<!DOCTYPE html>");
        url.append("<html>\n");
        url.append("<head>\n");
        url.append("    <meta charset=\"utf-8\">\n");
        url.append("    <meta value=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" />\n");
        url.append("    <title>").append(subject).append("</title>\n");
        url.append("    <link rel=\"stylesheet\" href=\"/css/wxpay.css\">\n");
        url.append("</head>\n");
        url.append("<body>\n");
        url.append("    <div class=\"main\">\n");
        url.append("        <div class=\"header\">\n");
        url.append("            <div class=\"logo wd\">\n");
        url.append("                <img src=\"/images/logo.png\" alt=\"\">\n");
        url.append("            </div>\n");
        url.append("            <h3 class=\" title\">").append(subject).append("</h3>\n");
        url.append("        </div>\n");
        url.append("        <div class=\"content wd\">\n");
        url.append("            <div class=\"code-box\">\n");
        url.append("                <div class=\"code-title\">\n");
        url.append("                    <h4>订单总计：<span>¥<b>").append(amount).append("元</b></span></h4>\n");
        url.append("                    <p>").append(body).append("</p>\n");
        url.append("                </div>\n");
        url.append("                <div class=\"code-img\">\n");
        url.append("                    <img src=\"/api/createQrcode?qrvalue=").append(codeURL).append("\" alt=\"\">\n");
        url.append("                </div>\n");
        url.append("                <div class='code-foot'>\n");
        url.append("                    <img src=\"/images/wei.png\" alt=\"\">\n");
        url.append("                    <span>请扫码完成微信支付</span>\n");
        url.append("                </div>\n");
        url.append("            </div>\n");
        url.append("        </div>\n");
        url.append("    </div>\n");
        url.append("</body>\n");
        url.append("<script src=\"/js/jquery-1.12.4.min.js\"></script>\n");
        url.append("<script>\n");
        url.append("    var orderid ='").append(orderid).append("';\n");
        url.append("    var mchId ='").append(mchId).append("';\n");
        url.append("    var returnUrl='").append(returnUrl).append("';\n");
        url.append("    var id = 0;\n");
        url.append("    var url = '/api/pay/isOrderFinish';\n");
        url.append("    $(function(){\n");
        url.append("        id= setInterval(load,1000);\n");
        url.append("    });\n");
        url.append("    function load() {\n");
        url.append("       $.ajax({\n");
        url.append("           url: url,\n");
        url.append("           type: 'post',\n");
        url.append("           contentType:'application/json',\n");
        url.append("           data: JSON.stringify({\n");
        url.append("                 \"payOrderId\":\"").append(orderid).append("\"").append(",\n");
        url.append("                 \"mchId\":\"").append(mchId).append("\"\n");
        url.append("           }),\n");
        url.append("           success: function (data) {\n");
        url.append("                 if(data == 'Y'){\n");
        url.append("                        clearInterval(id);\n");
        url.append("                        window.location = \"").append(returnUrl).append("\";\n");
        url.append("                 }\n");
        url.append("           },\n");
        url.append("           error: function (err) {\n");
        url.append("                 console.log(err);\n");
        url.append("           }\n");
        url.append("     });\n");
        url.append("  }\n");
        url.append("</script>\n");
        url.append("</html>\n");
        return url.toString();
    }

    /**
     * 校验传来的参数是否符合要求
     * @param payOrder 订单信息
     * @param resKey   需要返回的商户KEY
     * @throws BusinessException 校验不合格时抛出异常
     */
    private void validateParams(SunrealPayOrder payOrder, StringBuffer resKey) throws BusinessException {
        String mchid = payOrder.getMchid();
        String channelid = payOrder.getChannelid();
        String channelname = payOrder.getChannelname();
        AssertUtil.notBlank(mchid, "商户ID不能为空！");
        AssertUtil.notBlank(channelid, "支付渠道ID不能为空！");
        AssertUtil.notBlank(channelname, "支付渠道不能为空！");
        AssertUtil.notBlank(payOrder.getMchorderno(), "商户订单号不能为空！");
        AssertUtil.notBlank(payOrder.getAmount(), "金额不能为空！");
        AssertUtil.notBlank(payOrder.getNotifyurl(), "异步回调地址不能为空！");
        AssertUtil.notBlank(payOrder.getSubject(), "商品标题不能为空！");
        AssertUtil.notBlank(payOrder.getBody(), "商品描述不能为空！");
        AssertUtil.notBlank(payOrder.getReturnurl(), "同步回调不能为空！");
        AssertUtil.notBlank(payOrder.getOriginalorderid(), "原始订单编号不能为空！");
        AssertUtil.notBlank(payOrder.getSign(), "签名不能为空！");
        AssertUtil.notBlank(payOrder.getClientip(), "客户IP[clientIp]不能为空！");
        MchInfo mchInfo = baseService.baseSelectMchInfo(mchid);
        AssertUtil.notBlank(mchInfo, "数据库中无法找到商户信息[mchId=" + mchid + "]！");
        AssertUtil.notBlank(mchInfo.getReqkey(), "数据库中无法找到商户秘钥[mchId=" + mchid + "]！");
        if (mchInfo.getState() != 1) {
            throw new BusinessException(WebErrCode.ERROR_PARAMS.getErroCode(), "商户信息[mchId=" + mchid + "]不可用！");
        }
        resKey.append(mchInfo.getReskey());
        PayChannel payChannel = baseService.baseSelectPayChannelByMchIdAndChannelId(mchid, channelid);
        AssertUtil.notBlank(payChannel, "数据库中无法找到商户支付渠道信息[mchId=" + mchid + ",channelId=" + channelid + "]！");
        if (payChannel.getState() != 1) {
            throw new BusinessException(WebErrCode.ERROR_PARAMS.getErroCode(), "商户渠道信息[mchId=" + mchid + ",channelId=" + channelid + "]不可用！");
        }
        payOrder.setChannelmchid(payChannel.getChannelmchid());
        log.info("扩展参数" + payOrder.getExtra());
        if (channelname.equalsIgnoreCase(PayConstant.CHANNEL_NAME_WX)) {
            AssertUtil.notBlank(payOrder.getExtra(), "扩展参数不能为空！");
        }
        JSONObject extraObject;
        switch (channelid) {
            case PayConstant.PAY_CHANNEL_WX_JSAPI:
                extraObject = JSON.parseObject(payOrder.getExtra());
                String openId = extraObject.getString("openId");
                AssertUtil.notBlank(openId, "扩展参数OPENID[extra.openId]不能为空！");
                break;
            case PayConstant.PAY_CHANNEL_WX_NATIVE:
                extraObject = JSON.parseObject(payOrder.getExtra());
                String productId = extraObject.getString("productId");
                AssertUtil.notBlank(productId, "扩展参数商品ID[extra.productId]不能为空！");
                break;
            case PayConstant.PAY_CHANNEL_WX_MWEB:
                extraObject = JSON.parseObject(payOrder.getExtra());
                String sceneInfo = extraObject.getString("sceneInfo");
                AssertUtil.notBlank(sceneInfo, "扩展参数场景信息[extra.sceneInfo]不能为空！");
                AssertUtil.notBlank(payOrder.getClientip(), "客户IP[clientIp]不能为空！");
                break;
            default:
                break;
        }
    }
}
